package com.mrx.commons.gateway.handler.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.mrx.commons.gateway.config.GatewayProperties;
import com.mrx.commons.gateway.handler.DispatchHandler;
import com.mrx.commons.gateway.handler.HandlerChain;
import com.mrx.commons.gateway.model.Pair;
import com.mrx.commons.gateway.model.PathPatternPackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * @author Mr.X
 * @since 2024/6/2 上午9:47
 */
@Service
public class SpringServiceDispatchHandler extends DispatchHandler {

    @Resource
    private GatewayProperties properties;

    @Resource
    private ObjectMapper objectMapper;

    private static final PathMatcher pathMatcher = new AntPathMatcher();

    private static final Cache<String, Object> BEAN_CACHE = Caffeine.newBuilder()
            .expireAfterAccess(Duration.ofDays(1))
            .build();

    private static final Cache<String, Method> METHOD_CACHE = Caffeine.newBuilder()
            .expireAfterAccess(Duration.ofDays(1))
            .build();

    private static final Logger logger = LoggerFactory.getLogger(SpringServiceDispatchHandler.class);

    @Override
    public void dispatchRequest(HttpServletRequest request, HttpServletResponse response, HandlerChain chain) throws Exception {
        String requestURL = request.getRequestURL().toString();
        String requestURI = request.getRequestURI();
        logger.debug("dispatchRequest: {}", requestURL);
        Pair<String, String> pair = getMatchedPackage(requestURI).map(it -> getPackageName(it, requestURI)).orElse(null);
        if (pair != null) {
            dispatchSpringService(request, response, pair);
        }
    }

    private Optional<PathPatternPackage> getMatchedPackage(String requestURI) {
        return properties.getPathPatternPackages().stream().filter(it -> it.getPattern().contains("{"))
                .filter(it -> !pathMatcher.extractUriTemplateVariables(it.getPattern(), requestURI).isEmpty())
                .findFirst();
    }

    private Pair<String, String> getPackageName(PathPatternPackage pathPatternPackage, String requestURL) {
        String pattern = pathPatternPackage.getPattern();
        Map<String, String> uriTemplate = pathMatcher.extractUriTemplateVariables(pattern, requestURL);
        String result = pathPatternPackage.getPackageName();
        for (Map.Entry<String, String> entry : uriTemplate.entrySet()) {
            result = result.replace(String.format("{%s}", entry.getKey()), entry.getValue());
        }
        String serviceClazz = result.substring(0, result.lastIndexOf("."));
        String method = result.substring(result.lastIndexOf(".") + 1);
        return Pair.of(serviceClazz, method);
    }

    private void dispatchSpringService(HttpServletRequest request, HttpServletResponse response, Pair<String, String> it) throws Exception {
        logger.debug("dispatchSpringService: {}", it);
        Object bean = BEAN_CACHE.get(it.getKey(), this::getBean);
        Method method = METHOD_CACHE.get(String.format("%s.%s", it.getKey(), it.getValue()), key -> getMethod(bean, it.getValue()));
        Object result;
        if (Objects.requireNonNull(method).getParameterCount() != 0) {
            // TODO: 支持多参数
            Class<?>[] parameterTypes = method.getParameterTypes();
            Type[] genericParameterTypes = method.getGenericParameterTypes();
            if (genericParameterTypes.length != 1) {
                writeResponse(response, 500, "not support yet");
                return;
            }
            Class<?> type;
            if (genericParameterTypes[0] instanceof ParameterizedType) {
                type = (Class<?>) ((ParameterizedType) genericParameterTypes[0]).getActualTypeArguments()[0];
            } else {
                type = (Class<?>) genericParameterTypes[0];
            }
            Object param;
            if (Collection.class.isAssignableFrom(parameterTypes[0])) {
                param = objectMapper.readerFor(new TypeReference<Collection<?>>() {
                    @Override
                    public Type getType() {
                        return type;
                    }
                }).readValues(request.getReader()).readAll();
            } else {
                param = objectMapper.readValue(request.getReader(), type);
            }
            result = method.invoke(bean, param);
        } else {
            result = method.invoke(bean);
        }
        // TODO: 支持自定义 responseBodyHandler
        writeResponse(response, 200, objectMapper.writeValueAsString(result));
    }

    private Object getBean(String clazz) {
        try {
            return applicationContext.getBean(Class.forName(clazz));
        } catch (Exception e) {
            logger.warn("获取 bean 失败", e);
            throw new RuntimeException(e);
        }
    }

    private Method getMethod(Object bean, String methodName) {
        try {
            for (Method method : bean.getClass().getMethods()) {
                // TODO: 支持重载方法
                if (method.getName().equals(methodName)) {
                    return method;
                }
            }
            throw new IllegalArgumentException("找不到方法: " + methodName);
        } catch (Exception e) {
            logger.warn("获取 method 失败", e);
            throw new RuntimeException(e);
        }
    }

}
